﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using SStuff.WpfEffects;
using System.Windows.Media.Media3D;

namespace System.Windows.Media
{
    /// <summary>
    /// Provide a comparable functionality as the PlaneProjection of Silverlight
    /// </summary>
    public class PlaneProjection : DependencyObject
    {
        FrameworkElement _element;
        PlaneProjectionEffect _effect;
        ScaleTransform _scaleTransform;
        TranslateTransform _translateTransform;
        private void Attach(FrameworkElement element)
        {
            _element = element;
            _effect = new PlaneProjectionEffect() { RecenterViewPort = true };
            _element.Effect = _effect;


            _scaleTransform = new ScaleTransform();
            _translateTransform = new TranslateTransform();
            _element.RenderTransformOrigin = new Point(0.5, 0.5);
            _element.RenderTransform = new TransformGroup { Children = { _scaleTransform, _translateTransform } };
            _element.SizeChanged += _element_SizeChanged;
            UpdateRenderSize();

        }

        void _element_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            UpdateRenderSize();
            Commit();
        }

        private void UpdateRenderSize()
        {
            var bounds = VisualTreeHelper.GetDescendantBounds(_element);
            if (!bounds.IsEmpty)
                RenderSize = bounds.Size;
        }


        public Size RenderSize
        {
            get { return (Size)GetValue(RenderSizeProperty); }
            private set { SetValue(RenderSizeProperty, value); }
        }

        // Using a DependencyProperty as the backing store for RenderSize.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty RenderSizeProperty =
            DependencyProperty.Register("RenderSize", typeof(Size), typeof(PlaneProjection), new PropertyMetadata(new Size(1, 1)));







        private void Dettach(FrameworkElement element)
        {
            _element.SizeChanged -= _element_SizeChanged;
            _element = null;
            _element.Effect = null;
            _effect = null;

        }



        public Size ViewportSize
        {
            get { return (Size)GetValue(ViewportSizeProperty); }
            set { SetValue(ViewportSizeProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ViewportSize.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ViewportSizeProperty =
            DependencyProperty.Register("ViewportSize", typeof(Size), typeof(PlaneProjection), new PropertyMetadata(new Size(800, 600)));



        public bool AutoCommit
        {
            get { return (bool)GetValue(AutoCommitProperty); }
            set { SetValue(AutoCommitProperty, value); }
        }

        // Using a DependencyProperty as the backing store for AutoCommit.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty AutoCommitProperty =
            DependencyProperty.Register("AutoCommit", typeof(bool), typeof(PlaneProjection), new UIPropertyMetadata(true));



        public void Commit()
        {
            if (_element == null || _element.ActualWidth == 0 || _element.ActualHeight == 0 || ViewportSize.Width == 0)
                return;
            _effect.FieldOfView = 60.0 * RenderSize.Width / ViewportSize.Width;
            Vector3D v = new Vector3D(LocalOffsetX / RenderSize.Width, LocalOffsetY / RenderSize.Width, LocalOffsetZ / RenderSize.Width);

            //var amount = Math.Abs(Math.Sin(RotationZ*Math.PI/180.0));
            //var ratio = 1 + amount*(IdentityAspectRatio - 1);
            var invertRatio = RenderSize.Height / RenderSize.Width;
            _effect.SourceSize = new Size(1, invertRatio);
            Matrix3D computing = Matrix3D.Identity;



            computing.Translate(v);
            v.X = 1;
            v.Y = 0;
            v.Z = 0;
            Quaternion rotation = new Quaternion(v, -RotationX);
            v.X = 0;
            v.Y = 1;
            v.Z = 0;
            rotation *= new Quaternion(v, -RotationY);
            v.X = 0;
            v.Y = 0;
            v.Z = 1;
            rotation *= new Quaternion(v, -RotationZ);


            computing.RotateAt(rotation, new Point3D((CenterOfRotationX - .5) * 2.0, -2.0 * (CenterOfRotationY - .5), CenterOfRotationZ));

            v.X = GlobalOffsetX / RenderSize.Width;
            v.Y = GlobalOffsetY / RenderSize.Width;
            v.Z = GlobalOffsetZ * 10 / RenderSize.Width / _effect.FieldOfView;
            computing.Translate(v);


            _effect.PlaneTransformation = computing;
            _translateTransform.X = _effect.ViewPortOffset.X * RenderSize.Width * .32 *invertRatio;
            _translateTransform.Y = _effect.ViewPortOffset.Y * RenderSize.Height * .32 * invertRatio;

        }





        public double CenterOfRotationX
        {
            get { return (double)GetValue(CenterOfRotationXProperty); }
            set { SetValue(CenterOfRotationXProperty, value); }
        }

        // Using a DependencyProperty as the backing store for CenterOfRotationX.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CenterOfRotationXProperty =
            DependencyProperty.Register("CenterOfRotationX", typeof(double), typeof(PlaneProjection), new PropertyMetadata(0.5));





        public double CenterOfRotationY
        {
            get { return (double)GetValue(CenterOfRotationYProperty); }
            set { SetValue(CenterOfRotationYProperty, value); }
        }

        // Using a DependencyProperty as the backing store for CenterOfRotationY.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CenterOfRotationYProperty =
            DependencyProperty.Register("CenterOfRotationY", typeof(double), typeof(PlaneProjection), new PropertyMetadata(0.5));



        public double CenterOfRotationZ
        {
            get { return (double)GetValue(CenterOfRotationZProperty); }
            set { SetValue(CenterOfRotationZProperty, value); }
        }

        // Using a DependencyProperty as the backing store for CenterOfRotationZ.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CenterOfRotationZProperty =
            DependencyProperty.Register("CenterOfRotationZ", typeof(double), typeof(PlaneProjection), new PropertyMetadata(0.0));




        public double GlobalOffsetX
        {
            get { return (double)GetValue(GlobalOffsetXProperty); }
            set { SetValue(GlobalOffsetXProperty, value); }
        }

        // Using a DependencyProperty as the backing store for GlobalOffsetX.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty GlobalOffsetXProperty =
            DependencyProperty.Register("GlobalOffsetX", typeof(double), typeof(PlaneProjection), new PropertyMetadata(0.0));


        public double GlobalOffsetY
        {
            get { return (double)GetValue(GlobalOffsetYProperty); }
            set { SetValue(GlobalOffsetYProperty, value); }
        }

        // Using a DependencyProperty as the backing store for GlobalOffsetX.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty GlobalOffsetYProperty =
            DependencyProperty.Register("GlobalOffsetY", typeof(double), typeof(PlaneProjection), new PropertyMetadata(0.0));

        public double GlobalOffsetZ
        {
            get { return (double)GetValue(GlobalOffsetZProperty); }
            set { SetValue(GlobalOffsetZProperty, value); }
        }

        // Using a DependencyProperty as the backing store for GlobalOffsetX.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty GlobalOffsetZProperty =
            DependencyProperty.Register("GlobalOffsetZ", typeof(double), typeof(PlaneProjection), new PropertyMetadata(0.0));


        public double LocalOffsetX
        {
            get { return (double)GetValue(LocalOffsetXProperty); }
            set { SetValue(LocalOffsetXProperty, value); }
        }

        // Using a DependencyProperty as the backing store for LocalOffsetX.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty LocalOffsetXProperty =
            DependencyProperty.Register("LocalOffsetX", typeof(double), typeof(PlaneProjection), new PropertyMetadata(0.0));




        public double LocalOffsetY
        {
            get { return (double)GetValue(LocalOffsetYProperty); }
            set { SetValue(LocalOffsetYProperty, value); }
        }

        // Using a DependencyProperty as the backing store for LocalOffsetY.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty LocalOffsetYProperty =
            DependencyProperty.Register("LocalOffsetY", typeof(double), typeof(PlaneProjection), new PropertyMetadata(0.0));




        public double LocalOffsetZ
        {
            get { return (double)GetValue(LocalOffsetZProperty); }
            set { SetValue(LocalOffsetZProperty, value); }
        }

        // Using a DependencyProperty as the backing store for LocalOffsetZ.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty LocalOffsetZProperty =
            DependencyProperty.Register("LocalOffsetZ", typeof(double), typeof(PlaneProjection), new PropertyMetadata(0.0));




        public double RotationX
        {
            get { return (double)GetValue(RotationXProperty); }
            set { SetValue(RotationXProperty, value); }
        }

        // Using a DependencyProperty as the backing store for RotationX.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty RotationXProperty =
            DependencyProperty.Register("RotationX", typeof(double), typeof(PlaneProjection), new PropertyMetadata(0.0));




        public double RotationY
        {
            get { return (double)GetValue(RotationYProperty); }
            set { SetValue(RotationYProperty, value); }
        }

        // Using a DependencyProperty as the backing store for RotationY.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty RotationYProperty =
            DependencyProperty.Register("RotationY", typeof(double), typeof(PlaneProjection), new PropertyMetadata(0.0));




        public double RotationZ
        {
            get { return (double)GetValue(RotationZProperty); }
            set { SetValue(RotationZProperty, value); }
        }

        // Using a DependencyProperty as the backing store for RotationZ.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty RotationZProperty =
            DependencyProperty.Register("RotationZ", typeof(double), typeof(PlaneProjection), new PropertyMetadata(0.0));






        protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
        {
            base.OnPropertyChanged(e);
            if (AutoCommit)
                Commit();
        }











        public static PlaneProjection GetPlaneProjection(FrameworkElement obj)
        {
            return (PlaneProjection)obj.GetValue(PlaneProjectionProperty);
        }

        public static void SetPlaneProjection(FrameworkElement obj, PlaneProjection value)
        {
            obj.SetValue(PlaneProjectionProperty, value);
        }

        // Using a DependencyProperty as the backing store for PlaneProjection.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty PlaneProjectionProperty =
            DependencyProperty.RegisterAttached("PlaneProjection", typeof(PlaneProjection), typeof(PlaneProjection), new UIPropertyMetadata(null,
                (s, a) =>
                {
                    var elem = s as FrameworkElement;
                    if (elem == null)
                        return;
                    var oldProj = a.OldValue as PlaneProjection;
                    if (oldProj != null)
                        oldProj.Dettach(elem);

                    var newProj = a.NewValue as PlaneProjection;
                    if (newProj != null)
                        newProj.Attach(elem);
                }
                ));


    }
}
